排查API的connection reset by peer和Timeout exceeded问题 | 您所在的位置:网站首页 › 群晖connection reset by peer › 排查API的connection reset by peer和Timeout exceeded问题 |
排查API的connection reset by peer和Timeout exceeded问题
c_rain
· · 1337 次点击 ·
·
开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
第一次,站长亲自招 Gopher 了>>>
前言
这是一次实际生产中遇到的问题,根据问题模拟反复试验。看完这部分代码,您可以直接了解backlog的工作原理,以及系统调优时该怎么调,不至于胡乱设置。 排查API的connection reset by peer问题: 晚上22点~早上6点会偶尔出现,16:00出现过一次; Client.Timeout exceeded大量出现; 分析问题原因:connection reset by peer 会有几种情况出现: 处于ESTABLISHED 、 CLOSE_WAIT 、FIN_WAIT1 、FIN_WAIT2 、SYN_RECV或下一个发送序号并不是最后一个队列数据段序号并且是被动关闭的结束状态的rst状态; 处于TCP_SYN_SENT的rst状态;Client.Timeout exceeded 会有几种情况出现: 由于rst之后客户端重试时间过短导致的超时; 代码本身业务逻辑超时;- (该项不在本次验证范围) 怀疑对象 nginx 中 keepalive requests 配置 - 排除 内核参数net.core.somaxconn和net.ipv4.tcp_max_syn_backlog; - 排除 内核参数net.core.netdev_max_backlog - 影响不大只会导致丢包,消耗性能 网关nginx本身backlog和应用nginx本身backlog 网络内核参数用途介绍 #当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。这个参数表示该队列的最大值。 net.core.netdev_max_backlog = 262144 #用来限制监听(LISTEN)队列最大数据包的数量,超过这个数量就会导致链接超时或者触发重传机制。 net.core.somaxconn = 262144 #表示那些尚未收到客户端确认信息的连接(SYN消息)队列的长度,可以容纳更多等待连接的网络连接数。 net.ipv4.tcp_max_syn_backlog = 262144net.core.somaxconn 和 net.ipv4.tcp_max_syn_backlog用途 net.core.netdev_max_backlog用途linux内核代码 int netif_rx(struct sk_buff *skb) { struct softnet_data *queue; unsigned long flags; /* if netpoll wants it, pretend we never saw it */ if (netpoll_rx(skb)) return NET_RX_DROP; if (!skb->tstamp.tv64) net_timestamp(skb); /* * The code is rearranged so that the path is the most * short when CPU is congested, but is still operating. */ local_irq_save(flags); queue = &__get_cpu_var(softnet_data); __get_cpu_var(netdev_rx_stat).total++; if (queue->input_pkt_queue.qlen input_pkt_queue.qlen) { enqueue: dev_hold(skb->dev); __skb_queue_tail(&queue->input_pkt_queue, skb); local_irq_restore(flags); return NET_RX_SUCCESS; //成功 } napi_schedule(&queue->backlog); goto enqueue; } __get_cpu_var(netdev_rx_stat).dropped++; local_irq_restore(flags); kfree_skb(skb); return NET_RX_DROP; //删除包 }根据代码所示每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的最大数目,一旦超过将被丢弃。网卡阶段包容易导致dropped packet,容易造成丢包现象。 实验内容 模拟网关本身nginx配置 模拟线上sysctl内核参数 ab压测试验 golang编写http.NewRequest长连接请求压测试验 复现步骤查看内核参数 #systcl -a |grep -e netdev_max_backlog -e somaxconn -e tcp_max_syn_backlog
查看应用本身backlog #ss -l
可以先把nginx backlog调整成10,然后golang的client timeout设置成1golang代码 package main import ( "fmt" "io/ioutil" "net/http" "runtime" "time" ) var httpclient *http.Client func init(){ httpclient = &http.Client{Timeout:1 * time.Second} } func PostForm(url string) (body []byte, err error) { req, err := http.NewRequest("POST", url,nil) if err != nil { fmt.Println("NewRequest: ",err) return nil,err } req.Header.Set("Content-Type","application/x-www-form-urlencoded") resp, err := httpclient.Do(req) if err != nil { fmt.Println("httpclient: ",err) return nil,err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Bad HTTP Response: %v", resp.Status) } defer resp.Body.Close() return ioutil.ReadAll(resp.Body) } func runHttp(dd chan int,i int) { _, err := PostForm("http://192.168.1.107/index.php") if err != nil { fmt.Println("err: ",err) fmt.Println("执行中" ,i) } dd icsk_accept_queue->listen_opt->max_qlen_log; // syn 队列总长度,2^n max_syn_qlen = (1 sk_max_ack_backlog; printf("accept queue length limit: %d\n", max_acc_qlen) print_backtrace(); } }使用stap 执行上面的代码 # stap -g syn_backlog.c调试内核代码,捕获80端口的数据包。 经过多次验证得出结论半连接大小不会受到somaxconn和max_syn_backlog影响 somaxconn max_syn_backlog listen backlog 半连接队列大小 65535 65535 10 16 65535 65535 512 1025 65535 65535 65535 65536 65536 65536 65536 131072 181920 181920 181920 262144 在系统参数不修改的情形,盲目调大 listen 的 backlog 对最终半连接队列的大小不会影响。 在 listen 的 backlog 不变的情况下,不要盲目的调大 somaxconn 和 max_syn_backlog 对最终半连接队列的大小不会有影响 三次压测试验 #ab -r -c 200 -t300 http://192.168.1.107/index.php经过n次压测可以看到设置nginx默认511时,半连接开关为512。会偶尔出现错误情况,其实压测就200并发。设置backlog 262144时特别稳定。backlog 30时表现的则没那么好。 压测时特意抓了内核参数,发现其实和全队列有实际关系; ## RESET发送流程 图中会调用tcp_reset流程: tcp_reset函数 net/ipv4/tcp_input.c static void tcp_reset(struct sock *sk) { /* We want the right error as BSD sees it (and indeed as we do). */ switch (sk->sk_state) { case TCP_SYN_SENT: sk->sk_err = ECONNREFUSED; //报“Connection refused” 错 break; case TCP_CLOSE_WAIT: sk->sk_err = EPIPE; //报“Broken pipe” 错 break; case TCP_CLOSE: return; default: // TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | TCPF_SYN_RECV sk->sk_err = ECONNRESET; //报“Connection reset by peer” 错 } if (!sock_flag(sk, SOCK_DEAD)) sk->sk_error_report(sk); tcp_done(sk); } 1.TCP接收报文:在tcp_v4_rcv,如果校验和有问题,则发送RESET; 2.TCP接收报文:在tcp_v4_rcv,如果 __inet_lookup_skb 函数找不到报文所请求的socket,则发送RESET; 3.TCP收到SYN,发送SYN-ACK,并开始等待连接最后的ACK:在tcp_v4_do_rcv - tcp_v4_hnd_req - tcp_check_req,如果TCP报文头部包含RST,或者包含序列不合法的SYN,则发送RESET; 4.TCP收到连接建立最后的ACK,并建立child套接字后:tcp_v4_do_rcv - tcp_child_process - tcp_rcv_state_process - tcp_ack 函数中,如果发现连接等待的最后ACK序列号有问题: before(ack, prior_snd_una),则发送RESET; 5.TCP在ESTABLISH状态收到报文,在tcp_v4_do_rcv - tcp_rcv_established - tcp_validate_incoming 函数中,如果发现有SYN报文出现在当前的接收窗口中: th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt),则发送RESET; 6.TCP在进行状态迁移时:tcp_rcv_state_process - 如果此时socket处于LISTEN状态,且报文中含有ACK,则发送RESET; 如果此时socket处于FIN_WAIT_1或者FIN_WAIT_2;当接收已经shutdown,并且报文中有新的数据时,发送RESET; 如果测试socket处于FIN_WAIT_1;继续执行## close时调用tcp_disconnect的Connection reset by peer报错 int tcp_disconnect(struct sock *sk, int flags) { struct inet_sock *inet = inet_sk(sk); struct inet_connection_sock *icsk = inet_csk(sk); struct tcp_sock *tp = tcp_sk(sk); int err = 0; int old_state = sk->sk_state; if (old_state != TCP_CLOSE) tcp_set_state(sk, TCP_CLOSE); /* ABORT function of RFC793 */ if (old_state == TCP_LISTEN) { inet_csk_listen_stop(sk); } else if (tcp_need_reset(old_state) || // 处于ESTABLISHED 、 CLOSE_WAIT 、FIN_WAIT1 、FIN_WAIT2 、SYN_RECV或下一个发送序号并不是最后一个队列数据段序号 并且是被动关闭的结束状态的rst状态; (tp->snd_nxt != tp->write_seq && (1 sk_err = ECONNRESET; } else if (old_state == TCP_SYN_SENT) //处于TCP_SYN_SENT的rst状态 sk->sk_err = ECONNRESET; //...省略 return err; } 查看平常监控及报警
1.syn不会受到内核参数影响,只会受到list backlog影响;2.内核参数和list backlog不是随便乱调整的。3.nginx 除了Darwin系列系统和freebsd系列系统是由系统本身决定,其余的系统的backlog都是默认511。明显nginx的backlog比较低了。4.可以通过stap调试内核。 根据如上问题,其实可以看到一个情况。我们可以得知connection reset by peer罪魁祸手其实是内核(struct inet_connection_sock)->icsk_accept_queue->listen_opt->qlen参数导致的,其实是全队列。 整改建议1.调整内核参数部分: net.core.somaxconn = 262144 net.ipv4.tcp_max_syn_backlog = 262144 net.core.netdev_max_backlog = 2621442.调整nginx配置添加 server { listen 80 backlog=262144; }有疑问加站长微信联系(非本文作者) ![]() 本文来自:Segmentfault 感谢作者:c_rain 查看原文:排查API的connection reset by peer和Timeout exceeded问题 入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889 ![]() |
CopyRight 2018-2019 实验室设备网 版权所有 |